﻿using InvestmentIntelligence.Data.Repository;
using InvestmentIntelligence.Data.Repository.EFImplementation;
using InvestmentIntelligence.DbModel.Models;
using InvestmentIntelligence.Logging.Core;
using InvestmentIntelligence.MDP.Client;
using InvestmentIntelligence.TradingData.Adapters.Mus.Enums;
using InvestmentIntelligence.TradingData.Adapters.Mus.MarketIndicators.TrendIndicators;
using InvestmentIntelligence.TradingData.DataProcessing.DataResolvers;
using InvestmentIntelligence.UDS.Client;
using InvestmentIntelligence.UDS.JsonModels;
using System;
using System.Collections.Generic;
using System.Linq;

namespace InvestmentIntelligence.TradingData.Adapters.Mus.DataGenerators
{
    class TrendIndicatorResultsProcessor
    {
        private readonly FundBookEntity _book;
        private readonly IOriginalPositionRepository _originalPositionRepository;
        private readonly IBookSecurityStateRepository _bookSecurityStateRepository;
        private readonly FxSeriesClient _fxSerieClient;
        private readonly IFundBookSerieActiveRepository _fundBookSerieActiveRepository;

        private readonly SecurityInfoMapper _securityInfoMapper;
        private readonly TimeSerieInfoMapper _timeSerieInfoMapper;

        private struct SecurityTrendAction
        {
            public int Id { get; set; }
            public TrendTypes Type { get; set; }
            public TrendActions Action { get; set; }
        }

        public TrendIndicatorResultsProcessor(int bookId, SecurityInfoMapper securityInfoMapper, TimeSerieInfoMapper timeSerieInfoMapper)
        {
            _book = new FundBookRepository().Get(bookId);
            _originalPositionRepository = new OriginalPositionRepository();
            _bookSecurityStateRepository = new BookSecurityStateRepository();
            _fxSerieClient = new FxSeriesClient();
            _fundBookSerieActiveRepository = new FundBookActiveSerieRepository();

            _securityInfoMapper = securityInfoMapper;
            _timeSerieInfoMapper = timeSerieInfoMapper;
        }

        public void Process(Dictionary<int, List<TrendIndicatorResult>> indicatorResults)
        {
            var bookId = _book.Id;
            var currentPositions = _originalPositionRepository.GetActivePositions(bookId);
            var indicatorFinalResults = GetFinalTrendIndicatorResult(indicatorResults);
            var securityTrendActions = GetSecurityTrendActions(currentPositions, indicatorFinalResults);
            var actionsToUseCapital = securityTrendActions.Where(a => a.Action == TrendActions.Open || a.Action == TrendActions.Increase).ToList();
            var actionsToFreeCapital = securityTrendActions.Where(a => a.Action == TrendActions.Close || a.Action == TrendActions.Decrease).ToList();
            var securityIds = securityTrendActions.Select(s => s.Id).OrderBy(s => s).ToList();
            var securities = _securityInfoMapper.Get().Where(s => securityIds.BinarySearch(s.Id) > 0).ToDictionary(s => s.Id);
            var currencyIds = securities.Values.Select(s => s.CurrencyId).Union(new[] {_book.CurrencyId}).Distinct();
            var fxRates = new Dictionary<int, Dictionary<DateTime, decimal>>();
            foreach (var currencyId in currencyIds)
            {
                var rates = _fxSerieClient.Get(currencyId).ToDictionary(y => y.Date.Date, y => y.FxRateInUsd);
                fxRates.Add(currencyId, rates);
            }

            Dictionary<DateTime, decimal> bookFxRates;
            if (!fxRates.TryGetValue(_book.CurrencyId, out bookFxRates))
            {
                Log.Info("Cannot generate trades. Missing fxRates for book currency: {0}", _book.CurrencyId);
                return;
            }

            var trades = GenerateTrades(bookId, actionsToFreeCapital, actionsToUseCapital, currentPositions, securities, fxRates, bookFxRates);
            Log.Info("Generated new trades: {0}.", trades.Count);

            AddTrades(trades);
            Log.Debug("Saved new trades");
        }

        private void AddTrades(List<TradeModel> trades)
        {
            if (trades.Any()) return;
            
            var ids = new OriginalIdClient().Add(trades.Select(t => new TradeIdToOriginalIdModel { FundBookEntityId = t.FundBookEntityId, OriginalId = t.OriginalId }).Distinct())
                                              .ToDictionary(t => t.OriginalId);
            foreach (var t in trades)
            {
                var id = ids[t.OriginalId].TradeId;
                t.Id = id;
                if (t.ValidationData != null)
                {
                    t.ValidationData.TradeId = id;
                }
            }
            
            new TradeClient().Add(trades);
        }

        private List<TradeModel> GenerateTrades(int bookId, 
            List<SecurityTrendAction> actionsToFreeCapital, 
            List<SecurityTrendAction> actionsToUseCapital,
            Dictionary<int, OriginalPosition> currentPositions, 
            Dictionary<int, SecurityInfo> securities,
            Dictionary<int, Dictionary<DateTime, decimal>> fxRates, 
            Dictionary<DateTime, decimal> bookFxRates)
        {
            var trades = new List<TradeModel>();
            foreach (var action in actionsToFreeCapital)
            {
                var securityId = action.Id;
                decimal pos = 0;
                OriginalPosition p;
                if (currentPositions.TryGetValue(securityId, out p))
                {
                    pos = p.CloseQuantity.GetValueOrDefault();
                }

                var timeSerie = _timeSerieInfoMapper.Get(securityId).LastOrDefault();
                if (timeSerie == null) continue;

                var security = securities[securityId];
                Dictionary<DateTime, decimal> securityFxRates;
                if (!fxRates.TryGetValue(security.CurrencyId, out securityFxRates))
                {
                    Log.Info("Missing fxRates for currency: {0}", security.CurrencyId);
                    return trades;
                }

                var date = timeSerie.Date;
                decimal securityFxRate;
                decimal fundBookFxRate;
                if (!GetFxSerieRate(securityFxRates, date, out securityFxRate) ||
                    !GetFxSerieRate(bookFxRates, date, out fundBookFxRate))
                {
                    continue;
                }

                var price = timeSerie.ClosePrice*securityFxRate/fundBookFxRate;
                decimal quantity;
                switch (action.Action)
                {
                    case TrendActions.Close:
                        quantity = -pos;
                        break;
                    case TrendActions.Decrease:
                        quantity = -pos/2;
                        break;
                    default:
                        throw new NotSupportedException();
                }

                trades.Add(new TradeModel
                {
                    SecurityId = securityId,
                    Date = timeSerie.Date,
                    FundBookEntityId = bookId,
                    Quantity = quantity,
                    Price = price,
                    FxRate = 1m,
                    CurrencyId = _book.CurrencyId,
                    OriginalId = Guid.NewGuid().ToString(),
                });
            }

            var capitalToUse = GetActiveCapital()*0.1m; //todo: add percentage value to configs
            if (capitalToUse <= 0) return trades;

            var capitalPerAction = capitalToUse/actionsToUseCapital.Count;
            foreach (var action in actionsToUseCapital)
            {
                var securityId = action.Id;
                var timeSerie = _timeSerieInfoMapper.Get(securityId).LastOrDefault();
                if (timeSerie == null) continue;

                var security = securities[securityId];
                Dictionary<DateTime, decimal> securityFxRates;
                if (!fxRates.TryGetValue(security.CurrencyId, out securityFxRates))
                {
                    Log.Info("Missing fxRates for currency: {0}", security.CurrencyId);
                    return trades;
                }

                var date = timeSerie.Date;
                decimal securityFxRate;
                decimal fundBookFxRate;
                if (!GetFxSerieRate(securityFxRates, date, out securityFxRate) ||
                    !GetFxSerieRate(bookFxRates, date, out fundBookFxRate))
                {
                    continue;
                }

                var price = timeSerie.ClosePrice*securityFxRate/fundBookFxRate;
                decimal quantity;
                switch (action.Action)
                {
                    case TrendActions.Open:
                    case TrendActions.Increase:
                        quantity = Math.Floor(capitalPerAction/price);
                        if ((action.Type & TrendTypes.Short) > 0)
                        {
                            quantity *= -1;
                        }
                        break;
                    default:
                        throw new NotSupportedException();
                }

                trades.Add(new TradeModel
                {
                    SecurityId = securityId,
                    Date = timeSerie.Date,
                    FundBookEntityId = bookId,
                    Quantity = quantity,
                    Price = price,
                    FxRate = 1m,
                    CurrencyId = _book.CurrencyId,
                    OriginalId = Guid.NewGuid().ToString(),
                });
            }
            return trades;
        }

        private decimal GetActiveCapital()
        {
            var serie = _fundBookSerieActiveRepository.GetLastSerie(_book.Id);
            return serie.CloseActiveCapital ?? 0;
        }

        private static bool GetFxSerieRate(Dictionary<DateTime, decimal> fxRates, DateTime date, out decimal fxRate)
        {
            if (fxRates.TryGetValue(date, out fxRate))
            {
                return true;
            }

            var minDate = fxRates.Keys.Min(k => k);
            while (date >= minDate)
            {
                date = date.AddDays(-1);
                if (!fxRates.TryGetValue(date, out fxRate)) continue;

                return true;
            }

            return false;
        }

        private static Dictionary<int, List<TrendIndicatorResult>> GetFinalTrendIndicatorResult(Dictionary<int, List<TrendIndicatorResult>> indicatorResults)
        {
            var result = new Dictionary<int, List<TrendIndicatorResult>>();
            foreach (var indicatorResult in indicatorResults)
            {
                if (indicatorResult.Value.Count == 1)
                {
                    var r = indicatorResult.Value.First();
                    if (r.Results.Values.Any(v => v != TrendActions.None))
                    {
                        result.Add(indicatorResult.Key, new List<TrendIndicatorResult> { r });
                    }
                }
                else
                {
                    throw new NotImplementedException("No support for multiple indicators.");
                }
            }

            return result;
        }

        private List<SecurityTrendAction> GetSecurityTrendActions(Dictionary<int, OriginalPosition> currentPositions, Dictionary<int, List<TrendIndicatorResult>> indicatorFinalResults)
        {
            var securityTrendActions = new List<SecurityTrendAction>();
            var positionStates = _bookSecurityStateRepository.GetListByBook(_book.Id).ToDictionary(k => k.SecurityId, v => (TrendTypes)v.StateId);
            foreach (var indicatorResult in indicatorFinalResults)
            {
                var securityId = indicatorResult.Key;
                OriginalPosition p;
                decimal pos = 0;
                if (currentPositions.TryGetValue(securityId, out p))
                {
                    pos = p.CloseQuantity.GetValueOrDefault();
                }

                //todo: add support for multiple indicators here.
                var res = indicatorResult.Value.First();
                var indicatorActions = res.Results.Select(r => new SecurityTrendAction { Action = r.Value, Type = r.Key });
                var actionToPerform = new SecurityTrendAction {Id = securityId, Action = TrendActions.None};
                if (pos == 0)
                {
                    if (res.Results[TrendTypes.LongSmallTerm] == TrendActions.Open)
                    {
                        actionToPerform.Type = TrendTypes.LongSmallTerm;
                        actionToPerform.Action = TrendActions.Open;
                    }
                    else if (res.Results[TrendTypes.ShortSmallTerm] == TrendActions.Open)
                    {
                        actionToPerform.Type = TrendTypes.ShortSmallTerm;
                        actionToPerform.Action = TrendActions.Open;
                    }
                }
                else
                {
                    var curState = positionStates[securityId];
                    ProcessActionToPerform(actionToPerform, indicatorActions, curState);
                }

                if (actionToPerform.Action != TrendActions.None)
                {
                    securityTrendActions.Add(actionToPerform);
                }
            }
            return securityTrendActions;
        }

        private static void ProcessActionToPerform(SecurityTrendAction actionToPerform, IEnumerable<SecurityTrendAction> indicatorActions, TrendTypes currentState)
        {
            var isLongState = (currentState | TrendTypes.Long) > 0;
            if (isLongState)
            {
                var actions = indicatorActions.OrderByDescending(a => (int) a.Type).ToList();
                foreach (var action in actions)
                {
                    if (action.Action == TrendActions.None) continue;
                            
                    var isLongAction = (action.Type | TrendTypes.Long) > 0;
                    var isSmallTerm = (action.Type | TrendTypes.SmallTerm) > 0;
                    var isMediumTerm = (action.Type | TrendTypes.MediumTerm) > 0;
                    var isBigTerm = (action.Type | TrendTypes.BigTerm) > 0;

                    if (!isSmallTerm && !isMediumTerm &&!isBigTerm)
                    {
                        throw new NotImplementedException();
                    }
                    
                    switch (currentState)
                    {
                        case TrendTypes.LongSmallTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = action.Action;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            actionToPerform.Action = action.Action;
                                            break;
                                    }
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    //do nothing
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            break;
                        case TrendTypes.LongMediumTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Close:
                                            actionToPerform.Action = TrendActions.Close;
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;

                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            break;
                        case TrendTypes.LongBigTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Close:
                                            actionToPerform.Action = TrendActions.Close;
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    //do nothing
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    //do nothing
                                }
                            }
                            break;
                    }

                    if (actionToPerform.Action != TrendActions.None)
                    {
                        return;
                    }
                }
            }
            else
            {
                var actions = indicatorActions.OrderBy(a => (int)a.Type).ToList();
                foreach (var action in actions)
                {
                    if (action.Action == TrendActions.None) continue;

                    var isLongAction = (action.Type | TrendTypes.Long) > 0;
                    var isSmallTerm = (action.Type | TrendTypes.SmallTerm) > 0;
                    var isMediumTerm = (action.Type | TrendTypes.MediumTerm) > 0;
                    var isBigTerm = (action.Type | TrendTypes.BigTerm) > 0;

                    if (!isSmallTerm && !isMediumTerm && !isBigTerm)
                    {
                        throw new NotImplementedException();
                    }

                    switch (currentState)
                    {
                        case TrendTypes.ShortSmallTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    //do nothing
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Close:
                                            actionToPerform.Action = TrendActions.Close;
                                            break;
                                    }
                                }
                            }
                            break;
                        case TrendTypes.ShortMediumTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    //do nothing
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                            }
                            break;
                        case TrendTypes.ShortBigTerm:
                            if (isLongAction)
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    //do nothing
                                }
                            }
                            else
                            {
                                if (isBigTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            break;
                                        case TrendActions.Increase:
                                            actionToPerform.Action = TrendActions.Increase;
                                            break;
                                        case TrendActions.Decrease:
                                            actionToPerform.Action = TrendActions.Decrease;
                                            break;
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isMediumTerm)
                                {
                                    switch (action.Action)
                                    {
                                        case TrendActions.Open:
                                            actionToPerform.Action = TrendActions.Open;
                                            break;
                                        case TrendActions.Increase:
                                        case TrendActions.Decrease:
                                        case TrendActions.Close:
                                            break;
                                    }
                                }
                                else if (isSmallTerm)
                                {
                                    //do nothing
                                }
                            }
                            break;
                    }

                    if (actionToPerform.Action != TrendActions.None)
                    {
                        return;
                    }
                }
            }
        }
    }
}
